# coding:utf-8
# 元组
# 省略括号，用逗号分隔也是元组
tup = 4, 5, 6
print("省略括号:", tup)
# 元组是不可变的
tup = tuple(['foo', [1, 2], True])
print("包含list的元组:", tup)
# 如果元组中的某个对象是可变的，比如列表，则可以在原位修改
tup[1].append(3)
print("元组里的list修改:", tup)
# 串联元组
# 注意一个元素的序列，要在后面加一个逗号，否则会被认为单变量
print("串联元组:", (4, None, 'foo') + (6, 0) + ('bar', ))
# 元组乘以一个整数，像列表一样，会将几个元组的复制串联起来，对象本身没有被复制，只是引用了它
print("元组乘以标量:", ('foo', 'bar') * 4)
# 拆分元组，把元组复制给类似元组的变量，会试图拆分元组
tup = (4, 5, 6)
a, b, c = tup
print("拆分元组示例:", b)
# 含有元组的元组也会被拆分
tup = 4, 5, (6, 7)
a, b, (c, d) = tup
print("拆分元组示例2:", c)
# 使用拆分方法做变量替换
a, b = 1, 3
b, a = a, b
print("拆分方法替换变量:", a)
# 拆分方法常用来迭代元组或列表序列
seq = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
for a, b, c in seq:
    print('拆分迭代:' + 'a={0},b={1},c={2}'.format(a, b, c))
# Python允许从元组的开头“摘取”几个元素，使用*rest,剩下的元素在rest列表里
values = 1, 2, 3, 4, 5
a, b, *rest = values
print("*rest例子:", a, b, rest)

# tuple的方法，count统计某个值出现频率
a = (1, 2, 2, 2, 3, 4, 2)
print("数字2出现的频率:", a.count(2))

# ---------------------------------------------------------------
print("\n\n")
# 可变序列的方法
a = ['saw', 'small', 'He', 'foxes', 'six']
print("列表定义:", a)
# 属性操作类函数，改变序列本身
a.sort()  # 从小到大排序
print("sort默认方法(按字母从小到大):", a)
a.sort(key=len)  # 指定使用len方法获得的值排序，即长度
print("sort指定按len排序:", a)

a.append('He')  # 尾部添加元素
print("append方法:", a)
a.insert(2, 5)  # 指定位置2添加元素5
print("insert方法:", a)
a.remove('He')  # 移除第一个匹配
print("remove方法:", a)
d = a.pop(2)  # 删除指定位置元素，并返回该元素，无参数则处理最后一个元素
print('pop方法:', d)
print('pop后的序列:', a)
# 串联和组合列表
print("列表串联:", [4, None, 'foo'] + [7, 8, (2, 3)])
# 用一个list扩展另一个list,extend方法比串联更快
a.extend([6])  # 用另一个序列扩充原序列
print("extend扩展:", a)

# 二分搜索和维护已排序的列表
'''
    使用该方法时，要保证序列已经排序好，否则出错
'''
import bisect
c = [1, 2, 2, 2, 4, 7]
print(bisect.bisect(c, 2))  # 返回插入指定值的位置，如2应插入3个2之后，返回index
print("二分搜索结果1:", bisect.bisect(c, 5))
bisect.insort(c, 6)
print("二分搜索结果2:", c)

# 切片的结果是list，list[start:end:step=1] ;索引的结果是值
# 切⽚的起始元素是包括的，不包含结束元素。结果中包含的元素个数是     stop-start
print("切片例子1:", c[2:5])
# start或stop    都可以被省略，省略之后，分别默认序列的开头和结尾
# 当要选取最后一位时，要么切片的end空白，要么直接用-1进行索引
print("c[-2:-1]=", c[-2:-1], '\n', "c[-2:]=", c[-2:], '\n', "c[-1]=", c[-1])
# 切片赋值，小技巧，比如奇数位赋值,从而改变序列
c[2:6:2] = [8, 10]
print("切片赋值:", c)
# 负数表明从后向前切⽚
print("负数切片:", c[-2:])
# 颠倒序列的方法，c[::-1]
print("序列颠倒:", c[::-1])

# 序列函数，(包裹序列的函数)，enumerate(),sorted(),zip(),reversed()
# enumerate()可以跟踪当前项的序号
some_list = ['foo', 'bar', 'baz']
mapping = {}
for i, v in enumerate(some_list):
    mapping[i] = v  # 转字典
print("enumerate函数例子:", mapping)

# sorted()函数，返回一个排序好的新序列：
listorigin = [7, 1, 2, 6, 0, 3, 2]
print("sorted函数例子:", sorted(listorigin))

# zip()函数,将多个列表、元组或其他可迭代对象成对组合成一个元组列表,返回一个zip对象
# 对应位置配对成元组，打包成一个zip对象
seq1 = ['foo', 'bar', 'baz']
seq2 = ['one', 'two', 'three']
zipped = zip(seq1, seq2)
print(type(zipped), '\n', list(zipped))
# zip的常用用法，同时迭代多个序列，可结合enumerate使用
for i, (a, b) in enumerate(zip(seq1, seq2)):  # 注意zip的元素是元组列表，要用()扩起来
    print('zip结合enumerate:' + '{0}:{1},{2}'.format(i, a, b))
# 给出一个“被压缩的”序列，zip可以用来解压序列。嵌套序列，内层的同一个位置提取出来
# 可以当作：把行的列表转换成列的列表
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'), ('Schilling', 'Curt')]
# 函数参数里的*+序列，表示把序列分解成一个个的元素，作为多个参数
first_names, last_names = zip(*pitchers)  # 仍然获得的是元组
print("first_names:", first_names, '\n', "last_names:", last_names)

# reversed()函数，返回一个生成器，实体化(列表或for循环)后才能创建反转的序列
a = reversed(range(10))
print("reversed生成器:", type(a), '\n', list(a))

# ---------------------------------------------------------------
print("\n\n")
# 字典
d1 = {'a': 'some value', 'b': [1, 2, 3, 4]}
# 插⼊或设定字典中的元素：
d1[7] = 'an integer'
print("插入或设定后:", d1, '\n', "索引字典:", d1['b'])
print('b' in d1)  # 判断是否有某个键
# del或pop删除值
d1[5] = 'some value'
d1['dummy'] = 'another value'
print("插入2个元素后:", d1)
del d1[5]
print("删除索引5元素:", d1)
ret = d1.pop('dummy')
print("pop值:", ret, '\n', "pop后:", d1)
# 输出keys和values，按某一相同的顺序输出,不是常用list类型
print(d1.keys(), d1.values())
# 用update方法可以将一个字典与另一个融合：传递给update的键的旧值被舍弃
d1.update({'b': 'foo', 'c': 12})
print("update融合后:", d1)  # 原地修改

# 把两个序列配对组成字典，借助dict()和zip()函数。因为字典本身就是二元元组的集合。
mapping = dict(zip(range(5), reversed(range(5))))
print("序列配对组成字典:", mapping)

# 值求键的方法
sal_dep = {'人力部': 5500, '后勤部': 4500, '市场部': 6500, '开发部': 8500}
max_val = max(sal_dep.values())
max_name = [k for k, v in sal_dep.items()
            if v == max_val][0]  # 带判断的列表推导式求出所有的键，[0]索引第一个
print('工资最高的部门是:{},该部门工资是:{}'.format(max_name, max_val))

# 默认值设置
# get()返回的是一个值(是新对象)，存在键则返回对应值，不存在则返回第2个参数,若无第2参数，则返回None
value1 = d1.get('b', 'test')
value2 = d1.get('d', 'test')
value3 = d1.get('e')
print("get方法例子:", value1, value2, value3)
# setdefault()返回的是第一个参数的值或设置的默认值,后面接方法是原地操作，不是新对象在操作，
# example:通过首字母，将一个列表的单词分类
words = ['apple', 'bat', 'bar', 'atom', 'book']
by_letter = {}
for word in words:
    letter = word[0]
    '''
    使用for循环的写法
    if letter not in by_letter: # 首字母kety不存在时，第一个值赋予
        by_letter[letter]=[word]
    else: # 建立了首字母key后，直接append添加
        by_letter[letter].append(word)
    '''
    # 标准写法,letter这个key的默认值是空list，setdefault返回该key的值，此处是一个list，故可append
    by_letter.setdefault(letter, []).append(word)
print("setdefault用法：", by_letter)
# 借用collections模块里的defaultdict类,传递类型或函数以生成每个位置的默认值
from collections import defaultdict
by_letter = defaultdict(list)  # 字典里都是list类型的默认值，没有元素的时候就是空list
for word in words:
    by_letter[word[0]].append(word)
print(by_letter)

# 键的有效性，必须是不可变的，可hash()的,list要经过tuple()转为元组
d = {}
d[tuple([1, 2])] = 5
print(d)

# -------------------------------------------------------------
# set集合 :无序不可重复元素集合。像一个只有键的字典。使用set()或大括号创建
# 集合支持合并、交集、差分和对称差等数学集合运算。
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7, 8}
c = a.union(b)  # 取两集合不重复的元素,创建新的集合。也可使用‘|’
print("union或|的并运算:", a, c)
c = a.intersection(b)  # 交集，都有的元素，也可使用'&'
print("intersection或&的交运算:", a, c)
# 集合的操作均有原地实现方法，对大集合特别有用，先复制，然后原地实现方法形成新的集合
c = a.copy()
c |= b
print("=参与的增强运算达到原地实现:", c)
# 判断子集或父集
a_set = {1, 2, 3, 4, 5}
print("子集判断:", {1, 2, 3}.issubset(a_set))
print("父集判断:", a_set.issuperset({1, 2, 3}))

# --------------------------------------------------------------
# 列表、集合和字典推导式
# ----
# 列表推导式，允许用户方便的从一个集合过滤元素，形成列表，在传递参数的过程中还可以修改元素。
# expr for val in collection if condition]
strings = ['a', 'as', 'bat', 'car', 'dove', 'python']
liststr = [x.upper() for x in strings if len(x) > 2]
print("列表推导式:", liststr)
# ----
# 字典推导式：{key-expr:value-expr for value in collection if condition}
# 集合推导式：{expr for value in collection if condition}
# 获取字符串的长度
unique_lengths = {len(x) for x in strings}
print("集合推导式:", unique_lengths)
# 使用map()函数简化
unique_lengths = set(map(len, strings))
print("map函数同样实现:", unique_lengths)
# 创建一个字符串的查找引射表以确定它在列表中的位置：
loc_mapping = {val: index for index, val in enumerate(strings)}
print("字典推导式:", loc_mapping)

# 嵌套列表推导式，假设要从下面的名字里，找出含有至少2个e的名字组成一个list
all_data = [
    ['John', 'Emily', 'Michael', 'Mary', 'Steven'],
    ['Maria', 'Juan', 'Javier', 'Natalia', 'Pilar']
]
# 注意嵌套顺寻，二级嵌套的序列要是一级嵌套的元素。
result = [name for names in all_data for name in names if name.count('e') >= 2]
print("嵌套列表推导式:", result)
# 另一个例子 将一个整数元组的列表扁平化成一个整数列表：
some_tuples = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
flattened = [x for tup in some_tuples for x in tup]
print("嵌套列表推导式扁平化:", flattened)

# -------------------------------------------------------------
print('\n\n')
# 函数对象
# 如做数据清理,需要去除空白符、各种标点符号，正确的大写格式等
states = [
    '    Alabama', 'Georgia!', 'Georgia', 'georgia', 'FlOrIda',
    'south   carolina# #', 'West virginia?'
]
import re


def clean_strings(strings):
    result = []
    for value in strings:
        value = value.strip()  # 去除空白
        value = re.sub('[!# ?]', '', value)  # 将！、#、？都删除
        value = value.title()  # 标题格式，即首字母大写，其他小写
        result.append(value)
    return result


print("去除空白、特殊字符并首字母大写后:", clean_strings(states))


# 另一种做法：将需要在一组给定字符串上执行的所有运算做成一个列表
def remove_punctuation(value):
    return re.sub('[!# ?]', '', value)


clean_ops = [
    str.strip, remove_punctuation, str.title
]  # 方法名做为列表元素，strip,title都是str类的方法


def clean_strings(strings, ops):
    result = []
    for value in strings:
        for function in ops:
            value = function(value)
        result.append(value)
    return result


print("去除空白、特殊字符并首字母大写后2:", clean_strings(states, clean_ops))

# 函数做函数参数
for x in map(remove_punctuation, states):
    print("去除空白后:", x)

# 匿名函数lambda,单条语句构成，函数体结果就是返回值，可以赋值给变量。
# 假设有⼀组字符串，根据各字符串不同字⺟的数量对其进⾏排序：
strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
# 以传⼊⼀个lambda函数到列表的sort⽅法
strings.sort(key=lambda x: len(set(list(x))))


# 柯里化：部分参数应用-------通过“部分参数应用”从现有函数派生出新函数的技术。
def add_numbers(x, y):
    return x + y


add_five = lambda y: add_numbers(5, y)  # 派生出一个参数的函数-add_five,第二个参数称为“柯里化的”。其实是一个可以调用现有函数的新函数
# 借用内置的functools模块的partial偏函数,用来固定某个参数值
from functools import partial
add_five = partial(add_numbers, 5)

# 生成器
# 能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。
# 这是通过一种叫做迭代器协议的方式实现的，一个原生的使对象可迭代的方法。
# 大部分能接受列表之类的对象的方法都能作用于可迭代对象。
'''
生成器：构造新的可迭代对象的一种简单方式。一般的函数只返回单个值，而生成器则是以延迟的方式返回一个值列表,
即每返回一个值之后暂停，知道下一个值被请求时再继续。
要创建一个生成器，只需要将函数的return替换为yield就行。
'''


def squares(n=10):
    print('Generating squares from 1 to {0}'.format(n**2))
    for i in range(1, n + 1):
        yield i**2


gen = squares()  # 调用生成器时，没有任何代码会被立即执行：
print(gen)
# 直到你从该生成器请求元素时，它才开始执行代码
print("生成器展示元素:")
for x in gen:
    print(x, end=' ')
print('\n')

# 生成器表达式：把列表推导式两端的方括号改成圆括号
gen = (x**2 for x in range(100))
print(gen)
print("生成器做参数:", sum(gen))  # 作为函数参数
# ⽣成器表达式也可以取代列表推导式，作为函数参数,作为参数时，最外的括号不要
print("生成器表达式：", dict((i, i**2) for i in range(5)))

# itertools模块
'''
有一组用于许多常见数据算法的生成器。例如，groupby可以接受任何序列和一个函数。
它根据函数的返回值对序列中的连续元素进行分组。生成一个(key,sub-iterator)
'''
import itertools
first_letter = lambda x: x[0]
names = ['Alan', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
for letter, name in itertools.groupby(names, first_letter):
    print('groupby分组:', letter, list(name))  # name是一个生成器

items = [1, 2, 3]
# premutations()函数，考虑顺序组合元素，即数学中的排列，P-n-n,第2个数字key指定几元素组合。
for item in itertools.permutations(items):
    print("排列组合:", item)

# combinations()函数，不考虑顺序，不放回数据,即m中选n个，C-m-n
for item in itertools.combinations(items, 2):
    print("m个中选n个不同:", item)

# combinations_with_replacement()函数，不考虑顺序，有放回数据，即每次都是从m中选1个，选n次组成一个tuple
for item in itertools.combinations_with_replacement(items, 2):
    print("m个可重复选n个:", item)

# product()函数，笛卡尔积，针对多个输入序列进行排列组合
ab = ['a', 'b']
cd = ['c', 'd']
for item in itertools.product(ab, cd):
    print("多序列排列组合:", item)

# --------------------------------------------------------------
# 异常处理,试探执行try，出错执行except，若try成功，可执行else。另finally无论怎么样都执行
path = '1.txt'
f = open(path, 'r')
try:
    f.wirte("1")
except Exception:
    print('Failed')
else:
    print('Succeeded')
finally:
    f.close()

# --------------------------------------------------------------
# 文件操作
path = '1.txt'
f = open(path)  # 默认是以'r'模式
for line in f:  # 像处理列表那样来处理这个⽂件句柄
    print(line)
# 文件中读取的行都带有完成的行结束符(EOL)，使用rstrip()去除
lines = [x.rstrip() for x in open(path)]
print(lines)
# 如果用open创建文件对象，一定要用close关闭它。关闭文件返回操作系统资源
f.close()
# 使用with语句控制文件的打开关闭
with open(path) as f:
    lines = [x.rstrip() for x in f]
    print(lines)
